<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2.实战（二）：使用数据驱动框架绘制常用数据图表-使用 D3.js 绘制力导向图</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            padding: 0;
            margin: 0;
        }
        #stage {
            display: inline-block;
            width: 100%;
            height: 0;
            padding-bottom: 75%;
        }
        #stage canvas {
            background-color: #313131;
        }
    </style>
</head>
<body>
    用 D3.js 绘制图表，不同于使用 Qcharts，我们需要创建 SpriteJS 的容器。
    D3.js 在设计上采用了一些函数式编程思想
    
    <div id="stage"></div>

    <script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script>
    <script src="https://d3js.org/d3.v6.js"></script>

    <script type="module">
        const {Scene, Sprite, SpriteSvg } = spritejs;

        // 1.通过spritejs创建场景（作为其他元素的根元素容器）
        const container = document.getElementById("stage");
        // Canvas 对象的画布宽高设为 1000 * 800
        const scene = new Scene({
            container,
            width:1000,
            height:800,
            mode: 'stickyWidth',
        });

        const layer = scene.layer('fglayer', {
            handleEvent: false,
            autoRender: false,
        });
        
        // 创建d3的力模拟对象simulation
        const simulation = d3.forceSimulation()
            .force('link', d3.forceLink().id(d => d.id)) //节点连线 
            .force('charge', d3.forceManyBody()) // 多实体作用
            .force('center', d3.forceCenter(400, 300)); // 力中心

        // 通过d3.json方法读取数据
        d3.json('data.json').then(graph => {

            function ticked() {
                d3.select(layer).selectAll('path')
                .attr('d', (d) => {
                    const [sx, sy] = [d.source.x, d.source.y];
                    const [tx, ty] = [d.target.x, d.target.y];
                    return `M${sx} ${sy} L ${tx} ${ty}`;
                })
                .attr('strokeColor', 'white')
                .attr('lineWidth', 1);
                d3.select(layer).selectAll('sprite')
                .attr('pos', (d) => {
                    return [d.x, d.y];
                });
                layer.render();
            }

            // 通过力模型对象处理数据
            simulation
            .nodes(graph.nodes)
            .on('tick', ticked);


            simulation.force('link')
            .links(graph.links);


            // 绘制节点
            d3.select(layer).selectAll('sprite')
            .data(graph.nodes)
            .enter()
            .append('sprite')
            .attr('pos', (d) => {
                return [d.x, d.y];
            })
            .attr('size', [10, 10])
            .attr('border', [1, 'white'])
            .attr('borderRadius', 5)
            .attr('anchor', 0.5);

            // 绘制连线
            d3.select(layer).selectAll('path')
            .data(graph.links)
            .enter()
            .append('path')
            .attr('d', (d) => {
                const [sx, sy] = [d.source.x, d.source.y];
                const [tx, ty] = [d.target.x, d.target.y];
                return `M${sx} ${sy} L ${tx} ${ty}`;
            })
            .attr('name', (d, index) => {
                return `path${index}`;
            })
            .attr('strokeColor', 'white');


            d3.select(layer.canvas)
                .call(d3.drag()
                .container(layer.canvas)
                .subject(dragsubject)
                .on('start', dragstarted)
                .on('drag', dragged)
                .on('end', dragended));

            function dragsubject() {
                const [x, y] = layer.toLocalPos(event.x, event.y);
                return simulation.find(x, y);
            }

        });

        function dragstarted(event) {
            if(!event.active) simulation.alphaTarget(0.3).restart();

            const [x, y] = [event.subject.x, event.subject.y];
            event.subject.fx0 = x;
            event.subject.fy0 = y;
            event.subject.fx = x;
            event.subject.fy = y;

            const [x0, y0] = layer.toLocalPos(event.x, event.y);
            event.subject.x0 = x0;
            event.subject.y0 = y0;
        }

        function dragged(event) {
            const [x, y] = layer.toLocalPos(event.x, event.y),
                {x0, y0, fx0, fy0} = event.subject;
            const [dx, dy] = [x - x0, y - y0];

            event.subject.fx = fx0 + dx;
            event.subject.fy = fy0 + dy;
        }

        function dragended(event) {
            if(!event.active) simulation.alphaTarget(0);
            event.subject.fx = null;
            event.subject.fy = null;
        }


    </script>

</body>
</html>